// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/command_buffer/service/async_pixel_transfer_manager_share_group.h"

#include <list>

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/cancellation_flag.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/threading/thread_checker.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_synthetic_delay.h"
#include "gpu/command_buffer/service/async_pixel_transfer_delegate.h"
#include "ui/gl/gl_bindings.h"
#include "ui/gl/gl_context.h"
#include "ui/gl/gl_surface.h"
#include "ui/gl/gpu_preference.h"
#include "ui/gl/scoped_binders.h"

namespace gpu {

namespace {

    const char kAsyncTransferThreadName[] = "AsyncTransferThread";

    void PerformNotifyCompletion(
        AsyncMemoryParams mem_params,
        scoped_refptr<AsyncPixelTransferCompletionObserver> observer)
    {
        TRACE_EVENT0("gpu", "PerformNotifyCompletion");
        observer->DidComplete(mem_params);
    }

    // TODO(backer): Factor out common thread scheduling logic from the EGL and
    // ShareGroup implementations. http://crbug.com/239889
    class TransferThread : public base::Thread {
    public:
        TransferThread()
            : base::Thread(kAsyncTransferThreadName)
            , initialized_(false)
        {
            base::Thread::Options options;
#if defined(OS_ANDROID) || defined(OS_LINUX)
            options.priority = base::ThreadPriority::BACKGROUND;
#endif
            StartWithOptions(options);
        }

        ~TransferThread() override
        {
            // The only instance of this class was declared leaky.
            NOTREACHED();
        }

        void InitializeOnMainThread(gfx::GLContext* parent_context)
        {
            TRACE_EVENT0("gpu", "TransferThread::InitializeOnMainThread");
            if (initialized_)
                return;

            base::WaitableEvent wait_for_init(true, false);
            task_runner()->PostTask(
                FROM_HERE,
                base::Bind(&TransferThread::InitializeOnTransferThread,
                    base::Unretained(this), base::Unretained(parent_context),
                    &wait_for_init));
            wait_for_init.Wait();
        }

        void CleanUp() override
        {
            surface_ = NULL;
            context_ = NULL;
        }

    private:
        bool initialized_;

        scoped_refptr<gfx::GLSurface> surface_;
        scoped_refptr<gfx::GLContext> context_;

        void InitializeOnTransferThread(gfx::GLContext* parent_context,
            base::WaitableEvent* caller_wait)
        {
            TRACE_EVENT0("gpu", "InitializeOnTransferThread");

            if (!parent_context) {
                LOG(ERROR) << "No parent context provided.";
                caller_wait->Signal();
                return;
            }

            surface_ = gfx::GLSurface::CreateOffscreenGLSurface(gfx::Size(1, 1));
            if (!surface_.get()) {
                LOG(ERROR) << "Unable to create GLSurface";
                caller_wait->Signal();
                return;
            }

            // TODO(backer): This is coded for integrated GPUs. For discrete GPUs
            // we would probably want to use a PBO texture upload for a true async
            // upload (that would hopefully be optimized as a DMA transfer by the
            // driver).
            context_ = gfx::GLContext::CreateGLContext(parent_context->share_group(),
                surface_.get(),
                gfx::PreferIntegratedGpu);
            if (!context_.get()) {
                LOG(ERROR) << "Unable to create GLContext.";
                caller_wait->Signal();
                return;
            }

            context_->MakeCurrent(surface_.get());
            initialized_ = true;
            caller_wait->Signal();
        }

        DISALLOW_COPY_AND_ASSIGN(TransferThread);
    };

    base::LazyInstance<TransferThread>::Leaky
        g_transfer_thread
        = LAZY_INSTANCE_INITIALIZER;

    base::SingleThreadTaskRunner* transfer_task_runner()
    {
        return g_transfer_thread.Pointer()->task_runner().get();
    }

    class PendingTask : public base::RefCountedThreadSafe<PendingTask> {
    public:
        explicit PendingTask(const base::Closure& task)
            : task_(task)
            , task_pending_(true, false)
        {
        }

        bool TryRun()
        {
            // This is meant to be called on the main thread where the texture
            // is already bound.
            DCHECK(checker_.CalledOnValidThread());
            if (task_lock_.Try()) {
                // Only run once.
                if (!task_.is_null())
                    task_.Run();
                task_.Reset();

                task_lock_.Release();
                task_pending_.Signal();
                return true;
            }
            return false;
        }

        void BindAndRun(GLuint texture_id)
        {
            // This is meant to be called on the upload thread where we don't have to
            // restore the previous texture binding.
            DCHECK(!checker_.CalledOnValidThread());
            base::AutoLock locked(task_lock_);
            if (!task_.is_null()) {
                glBindTexture(GL_TEXTURE_2D, texture_id);
                task_.Run();
                task_.Reset();
                glBindTexture(GL_TEXTURE_2D, 0);
                // Flush for synchronization between threads.
                glFlush();
                task_pending_.Signal();
            }
        }

        void Cancel()
        {
            base::AutoLock locked(task_lock_);
            task_.Reset();
            task_pending_.Signal();
        }

        bool TaskIsInProgress()
        {
            return !task_pending_.IsSignaled();
        }

        void WaitForTask()
        {
            task_pending_.Wait();
        }

    private:
        friend class base::RefCountedThreadSafe<PendingTask>;

        virtual ~PendingTask() { }

        base::ThreadChecker checker_;

        base::Lock task_lock_;
        base::Closure task_;
        base::WaitableEvent task_pending_;

        DISALLOW_COPY_AND_ASSIGN(PendingTask);
    };

    // Class which holds async pixel transfers state.
    // The texture_id is accessed by either thread, but everything
    // else accessed only on the main thread.
    class TransferStateInternal
        : public base::RefCountedThreadSafe<TransferStateInternal> {
    public:
        TransferStateInternal(GLuint texture_id,
            const AsyncTexImage2DParams& define_params)
            : texture_id_(texture_id)
            , define_params_(define_params)
        {
        }

        bool TransferIsInProgress()
        {
            return pending_upload_task_.get() && pending_upload_task_->TaskIsInProgress();
        }

        void BindTransfer()
        {
            TRACE_EVENT2("gpu", "BindAsyncTransfer",
                "width", define_params_.width,
                "height", define_params_.height);
            DCHECK(texture_id_);

            glBindTexture(GL_TEXTURE_2D, texture_id_);
            bind_callback_.Run();
        }

        void WaitForTransferCompletion()
        {
            TRACE_EVENT0("gpu", "WaitForTransferCompletion");
            DCHECK(pending_upload_task_.get());
            if (!pending_upload_task_->TryRun()) {
                pending_upload_task_->WaitForTask();
            }
            pending_upload_task_ = NULL;
        }

        void CancelUpload()
        {
            TRACE_EVENT0("gpu", "CancelUpload");
            if (pending_upload_task_.get())
                pending_upload_task_->Cancel();
            pending_upload_task_ = NULL;
        }

        void ScheduleAsyncTexImage2D(
            const AsyncTexImage2DParams tex_params,
            const AsyncMemoryParams mem_params,
            scoped_refptr<AsyncPixelTransferUploadStats> texture_upload_stats,
            const base::Closure& bind_callback)
        {
            TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("gpu.AsyncTexImage");
            pending_upload_task_ = new PendingTask(base::Bind(
                &TransferStateInternal::PerformAsyncTexImage2D,
                this,
                tex_params,
                mem_params,
                texture_upload_stats));
            transfer_task_runner()->PostTask(
                FROM_HERE, base::Bind(&PendingTask::BindAndRun, pending_upload_task_, texture_id_));

            // Save the late bind callback, so we can notify the client when it is
            // bound.
            bind_callback_ = bind_callback;
        }

        void ScheduleAsyncTexSubImage2D(
            AsyncTexSubImage2DParams tex_params,
            AsyncMemoryParams mem_params,
            scoped_refptr<AsyncPixelTransferUploadStats> texture_upload_stats)
        {
            TRACE_EVENT_SYNTHETIC_DELAY_BEGIN("gpu.AsyncTexImage");
            pending_upload_task_ = new PendingTask(base::Bind(
                &TransferStateInternal::PerformAsyncTexSubImage2D,
                this,
                tex_params,
                mem_params,
                texture_upload_stats));
            transfer_task_runner()->PostTask(
                FROM_HERE, base::Bind(&PendingTask::BindAndRun, pending_upload_task_, texture_id_));
        }

    private:
        friend class base::RefCountedThreadSafe<TransferStateInternal>;

        virtual ~TransferStateInternal()
        {
        }

        void PerformAsyncTexImage2D(
            AsyncTexImage2DParams tex_params,
            AsyncMemoryParams mem_params,
            scoped_refptr<AsyncPixelTransferUploadStats> texture_upload_stats)
        {
            TRACE_EVENT2("gpu",
                "PerformAsyncTexImage",
                "width",
                tex_params.width,
                "height",
                tex_params.height);
            DCHECK_EQ(0, tex_params.level);

            base::TimeTicks begin_time;
            if (texture_upload_stats.get())
                begin_time = base::TimeTicks::Now();

            void* data = mem_params.GetDataAddress();

            {
                TRACE_EVENT0("gpu", "glTexImage2D");
                glTexImage2D(GL_TEXTURE_2D,
                    tex_params.level,
                    tex_params.internal_format,
                    tex_params.width,
                    tex_params.height,
                    tex_params.border,
                    tex_params.format,
                    tex_params.type,
                    data);
                TRACE_EVENT_SYNTHETIC_DELAY_END("gpu.AsyncTexImage");
            }

            if (texture_upload_stats.get()) {
                texture_upload_stats->AddUpload(base::TimeTicks::Now() - begin_time);
            }
        }

        void PerformAsyncTexSubImage2D(
            AsyncTexSubImage2DParams tex_params,
            AsyncMemoryParams mem_params,
            scoped_refptr<AsyncPixelTransferUploadStats> texture_upload_stats)
        {
            TRACE_EVENT2("gpu",
                "PerformAsyncTexSubImage2D",
                "width",
                tex_params.width,
                "height",
                tex_params.height);
            DCHECK_EQ(0, tex_params.level);

            base::TimeTicks begin_time;
            if (texture_upload_stats.get())
                begin_time = base::TimeTicks::Now();

            void* data = mem_params.GetDataAddress();
            {
                TRACE_EVENT0("gpu", "glTexSubImage2D");
                glTexSubImage2D(GL_TEXTURE_2D,
                    tex_params.level,
                    tex_params.xoffset,
                    tex_params.yoffset,
                    tex_params.width,
                    tex_params.height,
                    tex_params.format,
                    tex_params.type,
                    data);
                TRACE_EVENT_SYNTHETIC_DELAY_END("gpu.AsyncTexImage");
            }

            if (texture_upload_stats.get()) {
                texture_upload_stats->AddUpload(base::TimeTicks::Now() - begin_time);
            }
        }

        scoped_refptr<PendingTask> pending_upload_task_;

        GLuint texture_id_;

        // Definition params for texture that needs binding.
        AsyncTexImage2DParams define_params_;

        // Callback to invoke when AsyncTexImage2D is complete
        // and the client can safely use the texture. This occurs
        // during BindCompletedAsyncTransfers().
        base::Closure bind_callback_;
    };

} // namespace

class AsyncPixelTransferDelegateShareGroup
    : public AsyncPixelTransferDelegate,
      public base::SupportsWeakPtr<AsyncPixelTransferDelegateShareGroup> {
public:
    AsyncPixelTransferDelegateShareGroup(
        AsyncPixelTransferManagerShareGroup::SharedState* shared_state,
        GLuint texture_id,
        const AsyncTexImage2DParams& define_params);
    ~AsyncPixelTransferDelegateShareGroup() override;

    void BindTransfer() { state_->BindTransfer(); }

    // Implement AsyncPixelTransferDelegate:
    void AsyncTexImage2D(const AsyncTexImage2DParams& tex_params,
        const AsyncMemoryParams& mem_params,
        const base::Closure& bind_callback) override;
    void AsyncTexSubImage2D(const AsyncTexSubImage2DParams& tex_params,
        const AsyncMemoryParams& mem_params) override;
    bool TransferIsInProgress() override;
    void WaitForTransferCompletion() override;

private:
    // A raw pointer is safe because the SharedState is owned by the Manager,
    // which owns this Delegate.
    AsyncPixelTransferManagerShareGroup::SharedState* shared_state_;
    scoped_refptr<TransferStateInternal> state_;

    DISALLOW_COPY_AND_ASSIGN(AsyncPixelTransferDelegateShareGroup);
};

AsyncPixelTransferDelegateShareGroup::AsyncPixelTransferDelegateShareGroup(
    AsyncPixelTransferManagerShareGroup::SharedState* shared_state,
    GLuint texture_id,
    const AsyncTexImage2DParams& define_params)
    : shared_state_(shared_state)
    , state_(new TransferStateInternal(texture_id, define_params))
{
}

AsyncPixelTransferDelegateShareGroup::~AsyncPixelTransferDelegateShareGroup()
{
    TRACE_EVENT0("gpu", " ~AsyncPixelTransferDelegateShareGroup");
    state_->CancelUpload();
}

bool AsyncPixelTransferDelegateShareGroup::TransferIsInProgress()
{
    return state_->TransferIsInProgress();
}

void AsyncPixelTransferDelegateShareGroup::WaitForTransferCompletion()
{
    if (state_->TransferIsInProgress()) {
        state_->WaitForTransferCompletion();
        DCHECK(!state_->TransferIsInProgress());
    }

    // Fast track the BindTransfer, if applicable.
    for (AsyncPixelTransferManagerShareGroup::SharedState::TransferQueue::iterator
             iter
         = shared_state_->pending_allocations.begin();
         iter != shared_state_->pending_allocations.end();
         ++iter) {
        if (iter->get() != this)
            continue;

        shared_state_->pending_allocations.erase(iter);
        BindTransfer();
        break;
    }
}

void AsyncPixelTransferDelegateShareGroup::AsyncTexImage2D(
    const AsyncTexImage2DParams& tex_params,
    const AsyncMemoryParams& mem_params,
    const base::Closure& bind_callback)
{
    DCHECK(!state_->TransferIsInProgress());
    DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), tex_params.target);
    DCHECK_EQ(tex_params.level, 0);

    shared_state_->pending_allocations.push_back(AsWeakPtr());
    state_->ScheduleAsyncTexImage2D(tex_params,
        mem_params,
        shared_state_->texture_upload_stats,
        bind_callback);
}

void AsyncPixelTransferDelegateShareGroup::AsyncTexSubImage2D(
    const AsyncTexSubImage2DParams& tex_params,
    const AsyncMemoryParams& mem_params)
{
    TRACE_EVENT2("gpu", "AsyncTexSubImage2D",
        "width", tex_params.width,
        "height", tex_params.height);
    DCHECK(!state_->TransferIsInProgress());
    DCHECK_EQ(static_cast<GLenum>(GL_TEXTURE_2D), tex_params.target);
    DCHECK_EQ(tex_params.level, 0);

    state_->ScheduleAsyncTexSubImage2D(
        tex_params, mem_params, shared_state_->texture_upload_stats);
}

AsyncPixelTransferManagerShareGroup::SharedState::SharedState()
    // TODO(reveman): Skip this if --enable-gpu-benchmarking is not present.
    : texture_upload_stats(new AsyncPixelTransferUploadStats)
{
}

AsyncPixelTransferManagerShareGroup::SharedState::~SharedState() { }

AsyncPixelTransferManagerShareGroup::AsyncPixelTransferManagerShareGroup(
    gfx::GLContext* context)
{
    g_transfer_thread.Pointer()->InitializeOnMainThread(context);
}

AsyncPixelTransferManagerShareGroup::~AsyncPixelTransferManagerShareGroup() { }

void AsyncPixelTransferManagerShareGroup::BindCompletedAsyncTransfers()
{
    scoped_ptr<gfx::ScopedTextureBinder> texture_binder;

    while (!shared_state_.pending_allocations.empty()) {
        if (!shared_state_.pending_allocations.front().get()) {
            shared_state_.pending_allocations.pop_front();
            continue;
        }
        AsyncPixelTransferDelegateShareGroup* delegate = shared_state_.pending_allocations.front().get();
        // Terminate early, as all transfers finish in order, currently.
        if (delegate->TransferIsInProgress())
            break;

        if (!texture_binder)
            texture_binder.reset(new gfx::ScopedTextureBinder(GL_TEXTURE_2D, 0));

        // Used to set tex info from the gles2 cmd decoder once upload has
        // finished (it'll bind the texture and call a callback).
        delegate->BindTransfer();

        shared_state_.pending_allocations.pop_front();
    }
}

void AsyncPixelTransferManagerShareGroup::AsyncNotifyCompletion(
    const AsyncMemoryParams& mem_params,
    AsyncPixelTransferCompletionObserver* observer)
{
    // Post a PerformNotifyCompletion task to the upload thread. This task
    // will run after all async transfers are complete.
    transfer_task_runner()->PostTask(
        FROM_HERE, base::Bind(&PerformNotifyCompletion, mem_params, make_scoped_refptr(observer)));
}

uint32 AsyncPixelTransferManagerShareGroup::GetTextureUploadCount()
{
    return shared_state_.texture_upload_stats->GetStats(NULL);
}

base::TimeDelta
AsyncPixelTransferManagerShareGroup::GetTotalTextureUploadTime()
{
    base::TimeDelta total_texture_upload_time;
    shared_state_.texture_upload_stats->GetStats(&total_texture_upload_time);
    return total_texture_upload_time;
}

void AsyncPixelTransferManagerShareGroup::ProcessMorePendingTransfers()
{
}

bool AsyncPixelTransferManagerShareGroup::NeedsProcessMorePendingTransfers()
{
    return false;
}

void AsyncPixelTransferManagerShareGroup::WaitAllAsyncTexImage2D()
{
    if (shared_state_.pending_allocations.empty())
        return;

    AsyncPixelTransferDelegateShareGroup* delegate = shared_state_.pending_allocations.back().get();
    if (delegate)
        delegate->WaitForTransferCompletion();
}

AsyncPixelTransferDelegate*
AsyncPixelTransferManagerShareGroup::CreatePixelTransferDelegateImpl(
    gles2::TextureRef* ref,
    const AsyncTexImage2DParams& define_params)
{
    return new AsyncPixelTransferDelegateShareGroup(
        &shared_state_, ref->service_id(), define_params);
}

} // namespace gpu
